﻿using System;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Provider;
using System.Net;
using System.IO;
using System.Collections.Generic;

namespace FtpProvider
{
    [CmdletProvider("FTP", ProviderCapabilities.Credentials)]
    public class FtpProvider : NavigationCmdletProvider
    {
        #region Drive overloads
        protected override PSDriveInfo NewDrive(PSDriveInfo drive)
        {
            if (drive == null)
            {
                WriteError(
                    new ErrorRecord(
                        new ArgumentNullException("drive"),
                        "NullDrive",
                        ErrorCategory.InvalidArgument,
                        null
                    )
                );
                return null;
            }
            
            if (Uri.CheckHostName(drive.Root) == UriHostNameType.Unknown)
            {
                WriteError(
                    new ErrorRecord(
                        new ArgumentException("drive.Root"),
                        "InvalidRoot",
                        ErrorCategory.InvalidArgument,
                        drive
                    )
                );
                return null;
            }

            return new FtpDriveInfo(drive);
        }

        public class FtpDriveParameters
        {
            public int Port { get; set; }
        }

        protected override object NewDriveDynamicParameters()
        {
            return new FtpDriveParameters();
        }
        #endregion

        #region Powershell provider implementation

        #region DriveCmdletProvider overloads
        protected override bool IsValidPath(string path)
        {
            return Uri.IsWellFormedUriString(BuildUri(path), UriKind.Absolute);
        }

        protected override bool ItemExists(string path)
        {
            return ErrorWrapper(() => GetItemInfo(path) != null);
        }
        #endregion

        #region ItemCmdletProvider overrides
        protected override void GetItem(string path)
        {
            ErrorWrapper(() =>
            {
                var item = GetItemInfo(path);                
                WriteItemObject(item, path, item.IsFolder);                
            });           
        }

        protected override void SetItem(string path, object value)
        {
            // does not apply to FTP
            throw new NotImplementedException();
        }

        protected override void RemoveItem(string path, bool recurse)
        {
            ErrorWrapper(() => Delete(path));
        }
        #endregion

        #region ContainerCmdletProvider overrides
        protected override void GetChildItems(string path, bool recurse)
        {
            ErrorWrapper(() =>
            {
                foreach (var item in GetDirInfo(path))
                {
                    WriteItemObject(item, path, item.IsFolder);
                    if (recurse && item.IsFolder)
                    {
                        GetChildItems(item.Path, true);
                    }
                }
            });
        }

        protected override void GetChildNames(string path, ReturnContainers returnContainers)
        {
            GetChildItems(path, false);
        }

        protected override void RenameItem(string path, string newName)
        {
            if (Normalize(newName).Contains('/'))
            {
                WriteError(new ErrorRecord(new ArgumentException("Rename-Item cannot change the item's folder"), null, ErrorCategory.InvalidArgument, newName));
            }
            ErrorWrapper(() => Rename(path, newName));
        }

        protected override bool HasChildItems(string path)
        {
            return ErrorWrapper(() => GetDirInfo(path).Count > 0 );
        }
        #endregion

        #region NavigationCmdletProvider overrides
        protected override string MakePath(string parent, string child)
        {
            return Normalize(parent + '/' + child);
        }

        protected override string GetParentPath(string path, string root)
        {
            var normPath = Normalize(path);
            if (IsRootPath(normPath))
            {
                return "";
            }
            var childIndex = normPath.LastIndexOf('/');
            if (childIndex > 0)
            {
                return normPath.Remove(childIndex);                
            }
            else
            {
                return "";
            }
        }

        protected override bool IsItemContainer(string path)
        {
            return ErrorWrapper(() => GetItemInfo(path).IsFolder);
        }

        protected override void MoveItem(string path, string destination)
        {
            // command not implemented by .Net FtpWebRequest class
            throw new NotImplementedException();
        }

        protected override string NormalizeRelativePath(string path, string basePath)
        {
            // not totally sure what all the possible inputs are, so let the default implementation do the heavy 
            // lifting and just postprocess it
            return Normalize(base.NormalizeRelativePath(path, basePath));
        }
        #endregion

        #endregion

        #region Helper methods
        private string Normalize(string path)
        {
            return path.Replace('\\', '/');
        }

        private bool IsRootPath(string normPath)
        {
            throw new NotImplementedException();
        }
                        
        private void ErrorWrapper(Action a)
        {
            ErrorWrapper(() => { a(); return true; });
        }

        private bool ErrorWrapper(Func<bool> f)
        {
            try
            {
                return f();
            }
            catch (WebException ex)
            {
                WriteError(new ErrorRecord(ex, null, ErrorCategory.InvalidOperation, null));
                return false;
            }  
        }

        private string BuildUri(string path)
        {
            var drive = this.PSDriveInfo as FtpDriveInfo;

            path = Normalize(path);
            if (path.StartsWith(drive.Root))
                path = path.Replace(drive.Root, "");

            return new UriBuilder(Uri.UriSchemeFtp, drive.Root, drive.Port, path).ToString();
        }
        #endregion

        #region FTP Protocol implementation
        private FtpWebRequest PrepareRequest(string path, string method, bool binary = true, bool passive = false)
        {
            var request = (FtpWebRequest)FtpWebRequest.Create(BuildUri(path));
            request.Credentials = this.Credential.GetNetworkCredential();
            request.UseBinary = binary;
            request.UsePassive = passive;
            request.Method = method;
            return request;
        }
                
        public Stream Download(string path, bool binary, bool passive)
        {
            var request = PrepareRequest(path, WebRequestMethods.Ftp.DownloadFile, binary, passive);
            return request.GetResponse().GetResponseStream();
        }

        public void Upload(string path, Action<Stream> writer)
        {
            throw new NotImplementedException();
        }

        void Delete(string path)
        {
            throw new NotImplementedException();
        }

        FtpItemInfo GetItemInfo(string path)
        {
            throw new NotImplementedException();
        }

        List<FtpItemInfo> GetDirInfo(string path)
        {
            throw new NotImplementedException();
        }

        void MakeDir(string path)
        {
            throw new NotImplementedException();
        }

        void RemoveDir(string path)
        {
            throw new NotImplementedException();
        }

        void Rename(string oldPath, string newPath)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
}
